// =======================================================================================
/*!
 * @file    Util.c
 *
 * @brief   General utility functions.
 *
 * @author  Jorge Ramos (jramos at pobox dot com).
 *
 * @version 1.0.0.0 - May 23, 2011.
 *          @li Initial version.
 *
 * @remarks Copyright (c) 2011 Jorge Ramos.
 *
 * @remarks This software is provided 'as-is', without any express or implied warranty.
 *          Use it at your own risk. In no event shall the authors or copyright holders
 *          be held liable for any damages arising from the use of this software. Use
 *          and redistribution in source and binary forms, with or without modification,
 *          are permitted. Altered source versions must be plainly marked as such.
 *
 * @remarks This software is distributed in the hope that it will be useful, but WITHOUT
 *          ANY WARRANTY.
 */
// =======================================================================================

#include "Defs.h"
#include "Util.h"

//////////////////////////////////////////////////////////////////////////////////////////

static HINSTANCE m_hInstance;
static LPCTSTR m_pszApplicationName;
static LPCTSTR m_pszApplicationVersion;
static LPCTSTR m_pszApplicationAuthor;
static LPCTSTR m_pszApplicationNotice;

static UINT m_nTraceOutputFlag = TRACE_OUTPUT_NONE;
static TCHAR m_szTraceFileName [MAX_PATH];

//////////////////////////////////////////////////////////////////////////////////////////

VOID SetApplicationInstance (HINSTANCE hInstance)
{
    m_hInstance = hInstance;
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID SetApplicationName (LPCTSTR pszApplicationName)
{
    size_t nApplicationNameLen;
    DWORD dwChars;

    m_pszApplicationName = pszApplicationName;
    nApplicationNameLen = _tcslen (m_pszApplicationName);

    dwChars = GetEnvironmentVariable (_T ("TEMP"), m_szTraceFileName, _countof (m_szTraceFileName));

    if (dwChars > 0 && dwChars < _countof (m_szTraceFileName) - (nApplicationNameLen + 5)) // 5 => '\' + '.log'
    {
        if (m_szTraceFileName [dwChars - 1] != _T ('\\'))
        {
            m_szTraceFileName [dwChars++] = _T ('\\');
        }

        lstrcpyn (m_szTraceFileName + dwChars, m_pszApplicationName, _countof (m_szTraceFileName) - dwChars);
        lstrcpyn (m_szTraceFileName + dwChars + (DWORD) nApplicationNameLen, _T (".log"), _countof (m_szTraceFileName) - dwChars - (DWORD) nApplicationNameLen);
    }
    else
    {
        m_szTraceFileName [0] = _T ('\0');

        OutputDebugString (_T ("SetApplicationName: Trace file name too long\n"));
    }
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID SetApplicationVersion (LPCTSTR pszApplicationVersion)
{
    m_pszApplicationVersion = pszApplicationVersion;
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID SetApplicationAuthor (LPCTSTR pszApplicationAuthor)
{
    m_pszApplicationAuthor = pszApplicationAuthor;
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID SetApplicationNotice (LPCTSTR pszApplicationNotice)
{
    m_pszApplicationNotice = pszApplicationNotice;
}

//////////////////////////////////////////////////////////////////////////////////////////

HINSTANCE GetApplicationInstance (VOID)
{
    return m_hInstance;
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetApplicationName (VOID)
{
    return m_pszApplicationName;
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetApplicationVersion (VOID)
{
    return m_pszApplicationVersion;
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetApplicationAuthor (VOID)
{
    return m_pszApplicationAuthor;
}

//////////////////////////////////////////////////////////////////////////////////////////

LPCTSTR GetApplicationNotice (VOID)
{
    return m_pszApplicationNotice;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT _vsnwprintf2 (LPWSTR pszBuffer, size_t nCount, LPCWSTR pszFormat, va_list pArgs)
{
    INT nChars = _vsnwprintf (pszBuffer, nCount, pszFormat, pArgs);

    if (nChars < 0 || (size_t) nChars == nCount)
    {
        if (nCount > 0)
        {
            pszBuffer [nCount - 1] = L'\0';
        }

        nChars = -1;
    }

    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT _snwprintf2 (LPWSTR pszBuffer, size_t nCount, LPCWSTR pszFormat, ...)
{
    INT nChars;
    va_list pArgs;
    va_start (pArgs, pszFormat);

    nChars = _vsnwprintf2 (pszBuffer, nCount, pszFormat, pArgs);

    va_end (pArgs);
    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT _vsnprintf2 (LPSTR pszBuffer, size_t nCount, LPCSTR pszFormat, va_list pArgs)
{
    INT nChars = _vsnprintf (pszBuffer, nCount, pszFormat, pArgs);

    if (nChars < 0 || (size_t) nChars == nCount)
    {
        if (nCount > 0)
        {
            pszBuffer [nCount - 1] = '\0';
        }

        nChars = -1;
    }

    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT _snprintf2 (LPSTR pszBuffer, size_t nCount, LPCSTR pszFormat, ...)
{
    INT nChars;
    va_list pArgs;
    va_start (pArgs, pszFormat);

    nChars = _vsnprintf2 (pszBuffer, nCount, pszFormat, pArgs);

    va_end (pArgs);
    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatTextVaW (LPWSTR pszBuffer, size_t nCount, LPCWSTR pszFormat, va_list pArgs)
{
    INT nChars;

    nChars = _vsnwprintf2 (pszBuffer, nCount, pszFormat, pArgs);

    if (nChars < 0)
    {
        size_t nFormatLen = wcslen (pszFormat);

        if (nFormatLen > 0 && pszFormat [nFormatLen - 1] == L'\n')
        {
            if (nCount > 3) pszBuffer [nCount - 5] = L'.';
            if (nCount > 2) pszBuffer [nCount - 4] = L'.';
            if (nCount > 1) pszBuffer [nCount - 3] = L'.';
            if (nCount > 1) pszBuffer [nCount - 2] = L'\n';
        }
        else
        {
            if (nCount > 3) pszBuffer [nCount - 4] = L'.';
            if (nCount > 2) pszBuffer [nCount - 3] = L'.';
            if (nCount > 1) pszBuffer [nCount - 2] = L'.';
        }

        nChars = (INT) nCount - 1;
    }

    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatTextW (LPWSTR pszBuffer, size_t nCount, LPCWSTR pszFormat, ...)
{
    INT nChars;
    va_list pArgs;
    va_start (pArgs, pszFormat);

    nChars = FormatTextVaW (pszBuffer, nCount, pszFormat, pArgs);

    va_end (pArgs);
    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatTextVaA (LPSTR pszBuffer, size_t nCount, LPCSTR pszFormat, va_list pArgs)
{
    INT nChars;

    nChars = _vsnprintf2 (pszBuffer, nCount, pszFormat, pArgs);

    if (nChars < 0)
    {
        size_t nFormatLen = strlen (pszFormat);

        if (nFormatLen > 0 && pszFormat [nFormatLen - 1] == '\n')
        {
            if (nCount > 3) pszBuffer [nCount - 5] = '.';
            if (nCount > 2) pszBuffer [nCount - 4] = '.';
            if (nCount > 1) pszBuffer [nCount - 3] = '.';
            if (nCount > 1) pszBuffer [nCount - 2] = '\n';
        }
        else
        {
            if (nCount > 3) pszBuffer [nCount - 4] = '.';
            if (nCount > 2) pszBuffer [nCount - 3] = '.';
            if (nCount > 1) pszBuffer [nCount - 2] = '.';
        }

        nChars = (INT) nCount - 1;
    }

    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatTextA (LPSTR pszBuffer, size_t nCount, LPCSTR pszFormat, ...)
{
    INT nChars;
    va_list pArgs;
    va_start (pArgs, pszFormat);

    nChars = FormatTextVaA (pszBuffer, nCount, pszFormat, pArgs);

    va_end (pArgs);
    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatTextAnsiW (LPSTR pszBuffer, size_t nCount, LPCWSTR pszFormat, ...)
{
    LPWSTR pszTemp;
    INT nChars;
    va_list pArgs;
    va_start (pArgs, pszFormat);

    pszTemp = (LPWSTR) _alloca (nCount * sizeof (WCHAR));

    nChars = FormatTextVaW (pszTemp, nCount, pszFormat, pArgs);

    (VOID) WideCharToMultiByte (CP_ACP, 0, pszTemp, nChars + 1, pszBuffer, (INT) nCount, NULL, NULL);

    va_end (pArgs);
    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatTextAnsiA (LPSTR pszBuffer, size_t nCount, LPCSTR pszFormat, ...)
{
    INT nChars;
    va_list pArgs;
    va_start (pArgs, pszFormat);

    nChars = FormatTextVaA (pszBuffer, nCount, pszFormat, pArgs);

    va_end (pArgs);
    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatTextUnicodeW (LPWSTR pszBuffer, size_t nCount, LPCWSTR pszFormat, ...)
{
    INT nChars;
    va_list pArgs;
    va_start (pArgs, pszFormat);

    nChars = FormatTextVaW (pszBuffer, nCount, pszFormat, pArgs);

    va_end (pArgs);
    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatTextUnicodeA (LPWSTR pszBuffer, size_t nCount, LPCSTR pszFormat, ...)
{
    LPSTR pszTemp;
    INT nChars;
    va_list pArgs;
    va_start (pArgs, pszFormat);

    pszTemp = (LPSTR) _alloca (nCount * sizeof (CHAR));

    nChars = FormatTextVaA (pszTemp, nCount, pszFormat, pArgs);

    (VOID) MultiByteToWideChar (CP_ACP, 0, pszTemp, nChars + 1, pszBuffer, (INT) nCount);

    va_end (pArgs);
    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

static INT DisplayMessageBox (UINT uType, LPCTSTR pszFormat, va_list pArgs)
{
    TCHAR szBuffer [512];

    (VOID) FormatTextVa (szBuffer, _countof (szBuffer), pszFormat, pArgs);

    return MessageBox (NULL, szBuffer, m_pszApplicationName, uType);
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID DisplayInformation (LPCTSTR pszFormat, ...)
{
    va_list pArgs;

    va_start (pArgs, pszFormat);

    (VOID) DisplayMessageBox (MB_OK|MB_ICONINFORMATION, pszFormat, pArgs);

    va_end (pArgs);
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID DisplayError (LPCTSTR pszFormat, ...)
{
    va_list pArgs;
    va_start (pArgs, pszFormat);

    (VOID) DisplayMessageBox (MB_OK|MB_ICONSTOP, pszFormat, pArgs);

    va_end (pArgs);
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatWin32Message (DWORD dwMessageId, LPTSTR pszBuffer, size_t nCount)
{
    static const TCHAR szDefaultWin32Message [] = _T ("Win32 Message ID 0x%08lX (%ld)");
    static const DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS;

    INT nChars;
    LPTSTR pszMessage = NULL;
    DWORD dwLength = FormatMessage (dwFlags, NULL, dwMessageId, 0, (LPTSTR) &pszMessage, 0, NULL);

    if (pszMessage != NULL)
    {
        if (dwLength > 0)
        {
            while (pszMessage [dwLength - 1] == _T ('\n') || pszMessage [dwLength - 1] == _T ('\r'))
            {
                pszMessage [--dwLength] = _T ('\0');

                if (dwLength == 0)
                {
                    break;
                }
            }

            nChars = FormatText (pszBuffer, nCount, _T ("%s"), pszMessage);
        }
        else
        {
            nChars = FormatText (pszBuffer, nCount, szDefaultWin32Message, dwMessageId, dwMessageId);
        }

        LocalFree (pszMessage);
    }
    else
    {
        nChars = FormatText (pszBuffer, nCount, szDefaultWin32Message, dwMessageId, dwMessageId);
    }

    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

static VOID FormatWin32Message2 (DWORD dwMessageId, LPCTSTR pszMessageSource, LPTSTR pszBuffer, size_t nCount)
{
    size_t nBufferLen;

    --nCount; // Reserve space for for '\n'

    if (pszMessageSource != NULL)
    {
        lstrcpyn (pszBuffer, pszMessageSource, (INT) nCount);
        nBufferLen = _tcslen (pszBuffer);
        pszBuffer += nBufferLen;
        nCount -= nBufferLen;

        lstrcpyn (pszBuffer, _T (": "), (INT) nCount);
        nBufferLen = _tcslen (pszBuffer);
        pszBuffer += nBufferLen;
        nCount -= nBufferLen;
    }

    (VOID) FormatWin32Message (dwMessageId, pszBuffer, nCount);

    nBufferLen = _tcslen (pszBuffer);
    pszBuffer += nBufferLen;
    nCount -= nBufferLen;

    lstrcpyn (pszBuffer, _T ("\n"), (INT) nCount + 1); // Use reserved space
}

//////////////////////////////////////////////////////////////////////////////////////////

static VOID OutputDebugWin32Message (DWORD dwMessageId, LPCTSTR pszMessageSource)
{
    TCHAR szBuffer [512];

    FormatWin32Message2 (dwMessageId, pszMessageSource, szBuffer, _countof (szBuffer));

    OutputDebugString (szBuffer);
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID DisplayWin32Message (DWORD dwMessageId, LPCTSTR pszMessageSource)
{
    TCHAR szBuffer [512];

    FormatWin32Message (dwMessageId, szBuffer, _countof (szBuffer));

    if (pszMessageSource != NULL)
    {
        DisplayError (_T ("%s: %s\n"), pszMessageSource, szBuffer);
    }
    else
    {
        DisplayError (_T ("%s\n"), szBuffer);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////

INT FormatSystemMessage (INT nErrno, LPTSTR pszBuffer, size_t nCount)
{
#ifdef _UNICODE
    INT nChars = FormatText (pszBuffer, nCount, _T ("%hs"), strerror (nErrno));
#else
    INT nChars = FormatText (pszBuffer, nCount, _T ("%s"), strerror (nErrno));
#endif

    return nChars;
}

//////////////////////////////////////////////////////////////////////////////////////////

static VOID FormatSystemMessage2 (INT nErrno, LPCTSTR pszMessageSource, LPTSTR pszBuffer, size_t nCount)
{
    size_t nBufferLen;

    --nCount; // Reserve space for for '\n'

    if (pszMessageSource != NULL)
    {
        lstrcpyn (pszBuffer, pszMessageSource, (INT) nCount);
        nBufferLen = _tcslen (pszBuffer);
        pszBuffer += nBufferLen;
        nCount -= nBufferLen;

        lstrcpyn (pszBuffer, _T (": "), (INT) nCount);
        nBufferLen = _tcslen (pszBuffer);
        pszBuffer += nBufferLen;
        nCount -= nBufferLen;
    }

    (VOID) FormatSystemMessage (nErrno, pszBuffer, nCount);

    nBufferLen = _tcslen (pszBuffer);
    pszBuffer += nBufferLen;
    nCount -= nBufferLen;

    lstrcpyn (pszBuffer, _T ("\n"), (INT) nCount + 1); // Use reserved space
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID DisplaySystemMessage (INT nErrno, LPCTSTR pszMessageSource)
{
    TCHAR szBuffer [512];

    FormatSystemMessage (nErrno, szBuffer, _countof (szBuffer));

    if (pszMessageSource != NULL)
    {
        DisplayError (_T ("%s: %s\n"), pszMessageSource, szBuffer);
    }
    else
    {
        DisplayError (_T ("%s\n"), szBuffer);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID SetTraceOutput (UINT nFlag)
{
    m_nTraceOutputFlag = nFlag;
}

//////////////////////////////////////////////////////////////////////////////////////////

UINT GetTraceOutput (VOID)
{
    return m_nTraceOutputFlag;
}

//////////////////////////////////////////////////////////////////////////////////////////

static VOID WriteTraceString (HANDLE hTraceFile, LPCTSTR pszText)
{
    static const BYTE szByteOrderMark [2] = { 0xFF, 0xFE };
    BYTE szByteOrderMarkBuffer [2];
    size_t nTextLen;
    DWORD dwNumberOfBytesRead;
    LONG nDistanceToMoveHigh;
    LONG nDistanceToMove;
    LPCVOID pBuffer;
    LPBYTE pchBuffer;
    DWORD dwNumberOfBytesToWrite;
    DWORD dwNumberOfBytesWritten;

    if ((nTextLen = _tcslen (pszText)) == 0)
    {
        return;
    }

    if ((m_nTraceOutputFlag & TRACE_OUTPUT_CHKBOM) == 0)
    {
        if (! ReadFile (hTraceFile, szByteOrderMarkBuffer, 2, &dwNumberOfBytesRead, NULL))
        {
            OutputDebugWin32Message (GetLastError (), _T ("ReadFile"));
            return;
        }

        if (dwNumberOfBytesRead == 2)
        {
            if (szByteOrderMarkBuffer [0] == szByteOrderMark [0] && szByteOrderMarkBuffer [1] == szByteOrderMark [1])
            {
                m_nTraceOutputFlag &= ~TRACE_OUTPUT_ANSI;
            }
            else
            {
                m_nTraceOutputFlag |= TRACE_OUTPUT_ANSI;
            }
        }
        else if (dwNumberOfBytesRead == 1)
        {
            m_nTraceOutputFlag |= TRACE_OUTPUT_ANSI;
        }

        m_nTraceOutputFlag |= TRACE_OUTPUT_CHKBOM;
    }

    nDistanceToMoveHigh = 0;
    nDistanceToMove = SetFilePointer (hTraceFile, 0, &nDistanceToMoveHigh, FILE_END);

    if (nDistanceToMove == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR)
    {
        OutputDebugWin32Message (GetLastError (), _T ("SetFilePointer"));
        return;
    }

#ifdef _UNICODE

    if ((m_nTraceOutputFlag & TRACE_OUTPUT_ANSI) != 0)
    {
        INT nMultiByteTextLen = WideCharToMultiByte (CP_ACP, 0, pszText, (INT) nTextLen, NULL, 0, NULL, NULL);

        if (nMultiByteTextLen > 0)
        {
            LPSTR pszMultiByteText = (LPSTR) _alloca (nMultiByteTextLen * sizeof (CHAR));

            nMultiByteTextLen = WideCharToMultiByte (CP_ACP, 0, pszText, (INT) nTextLen, pszMultiByteText, nMultiByteTextLen, NULL, NULL);

            if (nMultiByteTextLen > 0)
            {
                pBuffer = pszMultiByteText; dwNumberOfBytesToWrite = (DWORD) nMultiByteTextLen * sizeof (CHAR);
            }
            else
            {
                OutputDebugWin32Message (GetLastError (), _T ("WideCharToMultiByte"));
                return;
            }
        }
        else
        {
            OutputDebugWin32Message (GetLastError (), _T ("WideCharToMultiByte"));
            return;
        }
    }
    else
    {
        if (nDistanceToMove == 0 && nDistanceToMoveHigh == 0)
        {
            dwNumberOfBytesToWrite = (INT) nTextLen * sizeof (WCHAR) + 2;
            pchBuffer = (LPBYTE) _alloca (dwNumberOfBytesToWrite);
            pchBuffer [0] = szByteOrderMark [0];
            pchBuffer [1] = szByteOrderMark [1];
            memcpy (pchBuffer + 2, pszText, nTextLen * sizeof (WCHAR));
            pBuffer = pchBuffer;
        }
        else
        {
            pBuffer = pszText; dwNumberOfBytesToWrite = (INT) nTextLen * sizeof (WCHAR);
        }
    }

#else

    if ((m_nTraceOutputFlag & TRACE_OUTPUT_ANSI) != 0)
    {
        pBuffer = pszText; dwNumberOfBytesToWrite = (DWORD) nTextLen * sizeof (CHAR);
    }
    else
    {
        INT nWideTextLen = MultiByteToWideChar (CP_ACP, 0, pszText, (INT) nTextLen, NULL, 0);

        if (nWideTextLen > 0)
        {
            LPWSTR pszWideText;
            size_t nExtraBytes;

            if (nDistanceToMove == 0 && nDistanceToMoveHigh == 0)
            {
                nExtraBytes = 2;
                pchBuffer = (LPBYTE) _alloca (nExtraBytes + nWideTextLen * sizeof (WCHAR));
                pchBuffer [0] = szByteOrderMark [0];
                pchBuffer [1] = szByteOrderMark [1];
                pszWideText = (LPWSTR) (pchBuffer + nExtraBytes);
            }
            else
            {
                nExtraBytes = 0;
                pszWideText = (LPWSTR) _alloca (nWideTextLen * sizeof (WCHAR));
            }

            nWideTextLen = MultiByteToWideChar (CP_ACP, 0, pszText, (INT) nTextLen, pszWideText, nWideTextLen);
            
            if (nWideTextLen > 0)
            {
                pBuffer = pszWideText; dwNumberOfBytesToWrite = (DWORD) nExtraBytes + nWideTextLen * sizeof (WCHAR);
            }
            else
            {
                OutputDebugWin32Message (GetLastError (), _T ("MultiByteToWideChar"));
                return;
            }
        }
        else
        {
            OutputDebugWin32Message (GetLastError (), _T ("MultiByteToWideChar"));
            return;
        }
    }

#endif

    if (! WriteFile (hTraceFile, pBuffer, dwNumberOfBytesToWrite, &dwNumberOfBytesWritten, NULL))
    {
        OutputDebugWin32Message (GetLastError (), _T ("WriteFile"));
        return;
    }

    if (dwNumberOfBytesWritten != dwNumberOfBytesToWrite)
    {
        OutputDebugWin32Message (ERROR_WRITE_FAULT, m_szTraceFileName);
        return;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID OutputTraceString (LPCTSTR pszText)
{
#ifdef _DEBUG
    OutputDebugString (pszText);
#else
    if ((m_nTraceOutputFlag & TRACE_OUTPUT_DEBUG) != 0)
    {
        OutputDebugString (pszText);
    }
#endif

    if ((m_nTraceOutputFlag & TRACE_OUTPUT_FILE) != 0 && m_szTraceFileName [0] != _T ('\0'))
    {
        static BOOL bLineIsTerminated = TRUE;
        BOOL bPrintDateTime;
        size_t nTextLen;
        size_t nCount;
        LPTSTR pszBuffer;
        SECURITY_ATTRIBUTES sa;
        HANDLE hTraceFile;
        INT nChars;

        bPrintDateTime = bLineIsTerminated;
        nTextLen = _tcslen (pszText);
        bLineIsTerminated = (nTextLen > 0 && pszText [nTextLen - 1] == _T ('\n'));

        if (bPrintDateTime)
        {
            SYSTEMTIME aSystemTime;
            TCHAR szDateTime [32];

            GetLocalTime (&aSystemTime);

            nChars = _sntprintf2 (szDateTime, _countof (szDateTime), _T ("%04d-%02d-%02d %02d:%02d:%02d.%03d"),
                                  aSystemTime.wYear, aSystemTime.wMonth, aSystemTime.wDay,
                                  aSystemTime.wHour, aSystemTime.wMinute, aSystemTime.wSecond, aSystemTime.wMilliseconds);
            if (nChars > 0)
            {
                nCount = nChars + nTextLen + 3; // 3 => ' ' + '\r' + '\0'
                pszBuffer = (LPTSTR) _alloca (nCount * sizeof (TCHAR));

                if (bLineIsTerminated)
                {
                    nChars = _sntprintf2 (pszBuffer, nCount, _T ("%s %.*s\r\n"), szDateTime, nTextLen - 1, pszText);
                }
                else
                {
                    nChars = _sntprintf2 (pszBuffer, nCount, _T ("%s %s"), szDateTime, pszText);
                }

                if (nChars > 0)
                {
                    pszText = (LPCTSTR) pszBuffer;
                }
                else
                {
                    OutputDebugWin32Message (ERROR_INSUFFICIENT_BUFFER, _T ("OutputTraceString"));
                }
            }
            else
            {
                OutputDebugWin32Message (ERROR_INSUFFICIENT_BUFFER, _T ("OutputTraceString"));
            }
        }
        else
        {
            if (bLineIsTerminated)
            {
                nCount = nTextLen + 2; // 2 => '\r' + '\0'
                pszBuffer = (LPTSTR) _alloca (nCount * sizeof (TCHAR));

                nChars = _sntprintf2 (pszBuffer, nCount, _T ("%.*s\r\n"), nTextLen - 1, pszText);

                if (nChars > 0)
                {
                    pszText = (LPCTSTR) pszBuffer;
                }
                else
                {
                    OutputDebugWin32Message (ERROR_INSUFFICIENT_BUFFER, _T ("OutputTraceString"));
                }
            }
        }

        sa.nLength = sizeof (sa);
        sa.bInheritHandle = FALSE;
        sa.lpSecurityDescriptor = NULL;

        hTraceFile = CreateFile (m_szTraceFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        if (hTraceFile != INVALID_HANDLE_VALUE)
        {
            WriteTraceString (hTraceFile, pszText);

            if (! CloseHandle (hTraceFile))
            {
                OutputDebugWin32Message (GetLastError (), _T ("CloseHandle"));
            }
        }
        else
        {
            OutputDebugWin32Message (GetLastError (), m_szTraceFileName);
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID TraceVa (LPCTSTR pszFormat, va_list pArgs)
{
    TCHAR szBuffer [512];

    (VOID) FormatTextVa (szBuffer, _countof (szBuffer), pszFormat, pArgs);

    OutputTraceString (szBuffer);
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID Trace (LPCTSTR pszFormat, ...)
{
    va_list pArgs;
    va_start (pArgs, pszFormat);

    (VOID) TraceVa (pszFormat, pArgs);

    va_end (pArgs);
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID TraceWin32Message (DWORD dwMessageId, LPCTSTR pszMessageSource)
{
    TCHAR szBuffer [512];

    FormatWin32Message2 (dwMessageId, pszMessageSource, szBuffer, _countof (szBuffer));

    OutputTraceString (szBuffer);
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID TraceSystemMessage (INT nErrno, LPCTSTR pszMessageSource)
{
    TCHAR szBuffer [512];

    FormatSystemMessage2 (nErrno, pszMessageSource, szBuffer, _countof (szBuffer));

    OutputTraceString (szBuffer);
}
//////////////////////////////////////////////////////////////////////////////////////////

BOOL Win32Call (BOOL bResult, LPCTSTR pszFunctionName)
{
    if (! bResult)
    {
        DWORD dwLastError = GetLastError ();
        TraceWin32Message (dwLastError, pszFunctionName);
        SetLastError (dwLastError);
    }

    return bResult;
}
